
#include "GLfont.h"

static int __CharCompFunc( const void* p1, const void* p2 )
{
	tCharInfo* pInfo1 = (tCharInfo*) p1;
	tCharInfo* pInfo2 = (tCharInfo*) p2;

	if( pInfo1->ch>pInfo2->ch )
		return 1;
	
	if( pInfo1->ch<pInfo2->ch )
		return -1;

	return 0;
}

static int __CharCompFuncByRefCount( const void* p1, const void* p2 )
{
	tCharInfo* pInfo1 = (tCharInfo*) p1;
	tCharInfo* pInfo2 = (tCharInfo*) p2;

	if( pInfo1->ref_count>pInfo2->ref_count )
		return 1;
	
	if( pInfo1->ref_count<pInfo2->ref_count )
		return -1;

	return 0;
}

void GLFontPage::Destroy()
{
	glDeleteTextures( 1,&m_texId );
	m_freeStack.c.clear();
}

bool GLFontPage::Create( int nCharWidth,int nCharHeight,int nPageWidth,int nPageHeight )
{
	int nCols = nPageWidth/nCharWidth ;
	int nRows = nPageHeight/nCharHeight ;

	int nSlot = nCols * nRows;

	for( int i = 0;i<nSlot;i++ )
		m_freeStack.push(nSlot-1-i);
	
	int nBytes = nPageWidth*nPageHeight*4 ;
	char* tmem = (char*)malloc( nBytes);
	
	for(int i = 0;i<nPageHeight;i++ ){		
		for(int j = 0;j<nPageWidth;j++ ){
			unsigned int* p = (unsigned int*)&tmem[(i*nPageWidth+j)*4]  ;
			*p = 0xff0000ff;
		}
	}

	glEnable(GL_TEXTURE_2D);

	glGenTextures(1,&m_texId);
	glBindTexture( GL_TEXTURE_2D,m_texId );

	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	
	glTexImage2D( GL_TEXTURE_2D,0,4,nPageWidth,nPageHeight,0,GL_RGBA,GL_UNSIGNED_BYTE,tmem );

	m_nPageWidth = nPageWidth;
	m_nPageHeight = nPageHeight;

	m_nCharWidth = nCharWidth;
	m_nCharHeight = nCharHeight;
	
	free(tmem);

	return true;
}

int GLFontPage::GetFreeSlot()
{
	if( m_freeStack.size() == 0 )
		return -1;
	int nSlot = m_freeStack.top();
	
	m_freeStack.pop();

	return nSlot;
}

void GLFontPage::FreeSlot( int slot )
{
	m_freeStack.push( slot );
}

void GLfont::AttachDC( HDC hDC )
{
	m_hdc = hDC;
}

void GLfont::BeginFrame( int nFrameCount )
{ 
	m_frameCount = nFrameCount;
	int nChars = m_charList.size();
	for( int i = 0;i<nChars;i++ ){
		m_charList[i].ref_count = 0;
	}
}
void GLfont::EndFrame()
{
	m_frameCountLast = m_frameCount;
}

void GLfont::DrawChar( float x_offset,float y_offset,tCharInfo* info )
{
	GLFontPage* page = m_pageList[info->page];
	
	glBindTexture( GL_TEXTURE_2D,page->m_texId );
#if 1
	glBegin(GL_QUADS );
	
	glTexCoord2f(info->u1,info->v2);
	glVertex2f( x_offset + info->x_org,y_offset-info->y_org+info->h );
	
	glTexCoord2f(info->u1,info->v1);
	glVertex2f( x_offset + info->x_org,y_offset-info->y_org );
	
	glTexCoord2f(info->u2,info->v1);
	glVertex2f( x_offset + info->x_org +info->w,y_offset-info->y_org );
	
	glTexCoord2f(info->u2,info->v2);
	glVertex2f( x_offset + info->x_org +info->w,y_offset-info->y_org+info->h );
	
	glEnd();
#endif
}

void GLfont::DrawGradChar( int mode,float x_offset,float y_offset,tCharInfo* info )
{
	GLFontPage* page = m_pageList[info->page];
	
	glBindTexture( GL_TEXTURE_2D,page->m_texId );
#if 1
	glBegin(GL_QUADS );
	

	//left-bottom
	if( mode == 0 ){
		glColor4f( m_textGradColor[0], m_textGradColor[1], m_textGradColor[2], m_textGradColor[3] );
	}
	else{
		glColor4f( m_textColor[0], m_textColor[1], m_textColor[2], m_textColor[3] );
	}
	glTexCoord2f(info->u1,info->v2);
	glVertex2f( x_offset + info->x_org,y_offset-info->y_org+info->h );

	//left-top
	
	glColor4f( m_textColor[0], m_textColor[1], m_textColor[2], m_textColor[3] );
	glTexCoord2f(info->u1,info->v1);
	glVertex2f( x_offset + info->x_org,y_offset-info->y_org );

	//right-top
	if( mode == 0 ){
		glColor4f( m_textColor[0], m_textColor[1], m_textColor[2], m_textColor[3] );
	}
	else{
		glColor4f( m_textGradColor[0], m_textGradColor[1], m_textGradColor[2], m_textGradColor[3] );
	}

	glTexCoord2f(info->u2,info->v1);
	glVertex2f( x_offset + info->x_org +info->w,y_offset-info->y_org );
	
	//right-bottom
	glColor4f( m_textGradColor[0], m_textGradColor[1], m_textGradColor[2], m_textGradColor[3] );
	glTexCoord2f(info->u2,info->v2);
	glVertex2f( x_offset + info->x_org +info->w,y_offset-info->y_org+info->h );
	
	glEnd();
#endif
}

void GLfont::Create( const LOGFONT* lf )
{	
	HFONT hf = ::CreateFontIndirect( lf );

	if(!m_hdc){
		HWND hwnd = ::GetDesktopWindow();
		HDC hdc = GetDC(hwnd );
		m_hdc = ::CreateCompatibleDC( hdc );
		::ReleaseDC( hwnd,hdc );
	}

	::SelectObject(m_hdc,hf); 

	AttachDC( m_hdc );
}

void GLfont::PrintGradText( int mode,float x,float y,const wchar_t* str)
{
	int nch = wcslen( str );

	glMatrixMode( GL_PROJECTION );
	glPushMatrix();
	glLoadIdentity();

	float vp[4];
	glGetFloatv( GL_VIEWPORT,vp );
	
	int sw = vp[2];
	int sh = vp[3];
	
	glOrtho( 0,sw,sh,0,-1,1);

	glMatrixMode( GL_MODELVIEW );
	glPushMatrix();
	glLoadIdentity();

	glTranslatef(0,0,0);

	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA );

	glEnable( GL_DEPTH_TEST );
	glDepthFunc( GL_ALWAYS );

	glDisable( GL_LIGHTING );

	float x_offset = x*sw;
	float y_offset = y*sh;

	glColor4f( m_textColor[0],m_textColor[1],m_textColor[2],m_textColor[3] ); 

	for( int i = 0;i<nch;i++ ){
		tCharInfo* info = CheckCharInfo( str[i] );
		info->ref_count++;
		DrawGradChar( mode,x_offset,y_offset,info );
		x_offset+=info->x_inc;
	}

	glMatrixMode( GL_PROJECTION );
	glPopMatrix();

	glMatrixMode( GL_MODELVIEW );
	glPopMatrix();
}
void GLfont::PrintText( float x,float y,const wchar_t* str)
{
	int nch = wcslen( str );

	glMatrixMode( GL_PROJECTION );
	glPushMatrix();
	glLoadIdentity();

	float vp[4];
	glGetFloatv( GL_VIEWPORT,vp );
	
	int sw = vp[2];
	int sh = vp[3];
	
	glOrtho( 0,sw,sh,0,-1,1);

	glMatrixMode( GL_MODELVIEW );
	glPushMatrix();
	glLoadIdentity();

	glTranslatef(0,0,0);

	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA );

	glEnable( GL_DEPTH_TEST );
	glDepthFunc( GL_ALWAYS );

	glDisable( GL_LIGHTING );

	float x_offset = x*sw;
	float y_offset = y*sh;

	glColor4f( m_textColor[0],m_textColor[1],m_textColor[2],m_textColor[3] ); 

	for( int i = 0;i<nch;i++ ){
		tCharInfo* info = CheckCharInfo( str[i] );
		info->ref_count++;
		DrawChar( x_offset,y_offset,info );
		x_offset+=info->x_inc;
	}

	glMatrixMode( GL_PROJECTION );
	glPopMatrix();

	glMatrixMode( GL_MODELVIEW );
	glPopMatrix();
}

void GLfont::PrintTextV( float x,float y, const wchar_t* fmt,...)
{
	if( m_frameCount - m_frameCountLast != 1 )
		return;

	wchar_t buf1[1024+1];
	va_list ap;
	va_start( ap,fmt );
	
	wchar_t* buf = buf1;

	int nch = _vscwprintf( fmt,ap );
	
	if( nch > 1024 ){
		buf = (wchar_t*)malloc( nch*sizeof(wchar_t) );
	}

	_vswprintf( buf,fmt,ap );
	
	PrintText( x,y,buf );

	if( nch > 1024 ){
		free( buf );
	}

	va_end( ap );
}

GLfont::GLfont()
{
	m_frameCount = -1;
	m_frameCountLast = -1;

	m_nCharWidth = 24;
	m_nCharHeight = 32;

	m_nMaxCharList = 1024;
	m_nMaxCharPage = 8;
	m_hdc = NULL;
	m_charMem = NULL;

	m_textColor[0] = m_textColor[1] = m_textColor[2] = 1.f;
	m_textColor[3] = 1.f;

	m_textGradColor[0] = m_textGradColor[1] = m_textGradColor[2] = 1.f;
	m_textGradColor[3] = 1.f;
}

GLfont::~GLfont()
{
	Release();
}

void GLfont::Release()
{
	if ( m_charMem ){
		free( m_charMem );
	}

	int nPages = m_pageList.size();
	for( int i = 0;i<nPages;i++ ){
		m_pageList[i]->Destroy();
		delete m_pageList[i];
		m_pageList[i] = NULL;
	}

	m_charList.clear();
}

void GLfont::SetMaxCharList( int nMaxCharList )
{
	m_nMaxCharList = nMaxCharList;
}

void GLfont::SetMaxCharPage( int nMaxCharPage )
{
	m_nMaxCharPage = nMaxCharPage;
}

tCharInfo* GLfont::FindCharInfo( wchar_t ch )
{
	tCharInfo info;
	info.ch = ch;

	if( m_charList.size() == 0 )
		return NULL;

	void*p=&m_charList[0];
	tCharInfo* pInfo = (tCharInfo* )bsearch( &info,&m_charList[0],m_charList.size(),sizeof( tCharInfo ),__CharCompFunc );
	
	return pInfo;
}

tCharInfo* GLfont::CheckCharInfo( wchar_t ch )
{
	tCharInfo* pInfo = FindCharInfo( ch );

	if(!pInfo ){
		pInfo = InsertCharInfo( ch );
	}

	return pInfo;
}

tCharInfo* GLfont::InsertCharInfo( wchar_t ch )
{
	int nPages = m_pageList.size();

	int iPage = -1;
	int iSlot = -1;

	tCharInfo* pCharInfo = NULL;
	GLFontPage*pPage = NULL;

	for( int i = 0;i<nPages;i++ ){
		int slot = m_pageList[i]->GetFreeSlot();
		if( slot >= 0 ){
			iSlot = slot;
			iPage = i;
			break;
		}
	}

	if(iSlot == -1 ){
		if( nPages < m_nMaxCharPage ){
			GLFontPage* pPage1 = new GLFontPage;
			pPage1->Create( m_nCharWidth,m_nCharHeight,256,256 );
			iSlot = pPage1->GetFreeSlot();
			iPage = (int) m_pageList.size();
			m_pageList.push_back( pPage1 );
		}
		else{
		
			int nChars = m_charList.size();
			std::vector<tCharInfo> charList;
		
			qsort( &m_charList[0],nChars,sizeof(tCharInfo),__CharCompFuncByRefCount);

			int nRemove = 64;
		
			if(nRemove > nChars ){ 
				nRemove = nChars; 
			}
	
			for( int i = 0;i<nRemove;i++ ){
				int page_index = m_charList[i].page;
				int page_slot = m_charList[i].slot;

				GLFontPage* p = m_pageList[page_index];
				p->FreeSlot( page_slot );

				iPage = page_index;
				iSlot = page_slot;
			}
			
			m_charList.erase( m_charList.begin(),m_charList.begin() + nRemove );
		}
	}

	pPage = m_pageList[iPage];

	GLYPHMETRICS gm;

	MAT2 mat2;

	mat2.eM11.value = 1;
	mat2.eM11.fract = 0;
	mat2.eM12.value = 0;
	mat2.eM12.fract = 0;
	mat2.eM21.value = 0;
	mat2.eM21.fract = 0;
	mat2.eM22.value = 1;
	mat2.eM22.fract = 0;

	memset(&gm,0,sizeof(GLYPHMETRICS));

	SetMapMode(m_hdc,MM_TEXT);

	int nbuff1 = GetGlyphOutline(m_hdc,ch,GGO_GRAY8_BITMAP,	&gm,0,NULL,&mat2);

	if( nbuff1 != GDI_ERROR ){
		if(!m_charMem ){
			 m_charMem = (char*)malloc(1024);
		}
		
		int nbuff2 = _msize(m_charMem);
		if(nbuff2<nbuff1){
			m_charMem = realloc(m_charMem,nbuff1);
		}
	
		GetGlyphOutline(m_hdc,ch,GGO_GRAY8_BITMAP,&gm,nbuff1,m_charMem,&mat2);

		int w = ALIGN_UP( gm.gmBlackBoxX,4 );
		int h = gm.gmBlackBoxY;

		int nCols = pPage->m_nPageWidth/pPage->m_nCharWidth;
		
		int x_offset = ( iSlot % nCols ) * pPage->m_nCharWidth;
		int y_offset = ( iSlot / nCols ) * pPage->m_nCharHeight;

		unsigned char* tmem = (unsigned char*)malloc( w*h*4 );
		unsigned char* char_mem = (unsigned char*)m_charMem;

		float gamma = 4.f;
		
		for( int i = 0;i<h;i++ ){
			for( int j = 0;j<w;j++ ){
				unsigned char* p = &tmem[(i*w+j)*4];
				p[0] = 0xff;
				p[1] = 0xff;
				p[2] = 0xff;
				float alpha = (float)char_mem[i*w+j];
				int val = (int)alpha*gamma;
				if( val > 255.f )val = 0xff;
				p[3] = (unsigned char) val;
			}
		}

		glBindTexture( GL_TEXTURE_2D,pPage->m_texId );
		glTexSubImage2D(GL_TEXTURE_2D,0,x_offset,y_offset,w,h,GL_RGBA,GL_UNSIGNED_BYTE,tmem);

		free( tmem );

		tCharInfo info;

		info.ch = ch;

		info.slot = iSlot;
		info.page = iPage;
		
		info.u1 = (float)x_offset/(float)pPage->m_nPageWidth;
		info.v1 = (float)y_offset/(float)pPage->m_nPageHeight;

		info.u2 = info.u1 + (float)gm.gmBlackBoxX/(float)pPage->m_nPageWidth;
		info.v2 = info.v1 + (float)gm.gmBlackBoxY/(float)pPage->m_nPageHeight;

		info.w = gm.gmBlackBoxX;
		info.h = gm.gmBlackBoxY;

		info.x_inc = gm.gmCellIncX;
		info.y_inc = gm.gmCellIncY;

		info.x_org = gm.gmptGlyphOrigin.x;
		info.y_org = gm.gmptGlyphOrigin.y;

		info.ref_count = 0;

		m_charList.push_back( info );
		
		pCharInfo = &m_charList[m_charList.size()-1];
		tCharInfo key = *pCharInfo;
		
		qsort( &m_charList[0],m_charList.size(),sizeof(tCharInfo),__CharCompFunc );

		pCharInfo = (tCharInfo* )bsearch( &key,&m_charList[0],m_charList.size(),sizeof( tCharInfo ),__CharCompFunc );
	
	}
	
	return pCharInfo;
}